package editorsvg.editor.documento;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.List;

/**
 * Documento que puede ser editado en con EditorDocumento.
 *
 * @author Jorge Berjano
 */
public class ElementoEditable extends ElementoGrafico {

    private ListaElementos listaElementos = new ListaElementos();

    
    public Object clone() throws CloneNotSupportedException {
        ElementoEditable clon = (ElementoEditable) super.clone();
        clon.listaElementos = (ListaElementos) listaElementos.clone();
        return clon;
    }
    
    public boolean contieneElemento(final ElementoGrafico simbolo) {
        boolean recorridoCompleto = recorrer(new Visitante() {

                @Override
                public boolean visitar(ElementoGrafico elemento) {
                    return elemento != simbolo;
                }
        });
        
        return !recorridoCompleto;
    }

    @Override
    public Shape getContorno() {
        Shape contorno = listaElementos.getContorno();
        if (getTransformacion().getAffineTransform() != null) {
            contorno = getTransformacion().getAffineTransform().createTransformedShape(contorno);
        }
        return contorno;
    }
    private boolean repintar;

    public boolean hayQueRepintar() {
        if (repintar) {
            return true;
        } else {
            return false;
        }
    }

    public void repintar() {
        repintar = true;
    }

    @Override
    public void pintar(Graphics2D graphics) {
        AffineTransform transfomacionInicial = graphics.getTransform();
        if (getTransformacion().getAffineTransform() != null) {
            graphics.transform(getTransformacion().getAffineTransform());
        }
        
        listaElementos.pintar(graphics);
        repintar = false;
        
        graphics.setTransform(transfomacionInicial);
    }

    @Override
    public void actualizar() {
        listaElementos.actualizar();
    }

    public void agregarElemento(ElementoGrafico nodoGrafico) {
        listaElementos.agregar(nodoGrafico);
        repintar();
    }

    public void agregarElemento(int indice, ElementoGrafico nodoGrafico) {
        listaElementos.agregar(indice, nodoGrafico);
        repintar();
    }

    public ListaElementos getListaElementos() {
        return listaElementos;
    }

    @Override
    public boolean recorrer(Visitante visitante) {
        if (!super.recorrer(visitante)) {
            return false;
        }
        return listaElementos.recorrer(visitante);
    }

    public boolean iterarAscendente(Visitante visitante) {
        return listaElementos.iterarAscendente(visitante);
    }

    public boolean iterarDescendente(Visitante visitante) {
        return listaElementos.iterarDescendente(visitante);
    }

    public void traerAlFrente(ConjuntoElementos elementos) {
        listaElementos.traerAlFrente(elementos);
        repintar();
    }

    public void llevarAlFondo(ConjuntoElementos elementos) {
        listaElementos.llevarAlFondo(elementos);
        repintar();
    }

    public void subir(ConjuntoElementos elementos) {
        listaElementos.subir(elementos);
        repintar();
    }

    public void bajar(ConjuntoElementos elementos) {
        listaElementos.bajar(elementos);
        repintar();
    }

    public ConjuntoElementos puedenSubir(ConjuntoElementos elementos) {
        return listaElementos.puedenSubir(elementos);
    }

    public ConjuntoElementos puedenBajar(ConjuntoElementos elementos) {
        return listaElementos.puedenBajar(elementos);
    }

    /**
     * Borra un nodo gráfico del documento.
     */
    public void borrarElemento(ElementoGrafico elemento) {
        listaElementos.borrar(elemento);
        repintar();
    }

    public void borrarElementos(ConjuntoElementos elementos) {
        listaElementos.borrar(elementos);
        repintar();
    }

    public void borrarElementos(List<ElementoGrafico> elementos) {
        listaElementos.borrar(elementos);
        repintar();
    }

    public ElementoGrupo agruparElementos(ConjuntoElementos elementosAAgrupar) {
        ElementoGrupo grupo = new ElementoGrupo();

        for (ElementoGrafico elemento : listaElementos.getLista()) {
            if (elementosAAgrupar.contiene(elemento)) {
                grupo.agregarElemento(elemento);
            }
        }

        agregarElemento(grupo);
        borrarElementos(elementosAAgrupar);

        repintar();
        return grupo;
    }

    public ConjuntoElementos desagruparElementos(ConjuntoElementos elementosADesagrupar) {
        ConjuntoElementos elementosDesagrupados = new ConjuntoElementos();
        for (ElementoGrafico elemento : elementosADesagrupar.getLista()) {
            if (!(elemento instanceof ElementoGrupo)) {
                continue;
            }
            int indice = listaElementos.getLista().indexOf(elemento);
            listaElementos.borrar(elemento);
            ElementoGrupo grupo = (ElementoGrupo) elemento;
            for (ElementoGrafico hijo : grupo.getListaElementos().getLista()) {
                listaElementos.agregar(indice++, hijo);
                elementosDesagrupados.agregar(hijo);
            }
        }
        repintar();
        return elementosDesagrupados;
    }

    public ElementoGrafico obtenerElementoEn(final Point2D punto, double tolerancia) {
        SeleccionadorPunto seleccionador = new SeleccionadorPunto(punto, tolerancia);
        getListaElementos().iterarDescendente(seleccionador);
        return seleccionador.getSeleccionado();
    }

    public ListaElementos obtenerElementoEn(Rectangle2D rectangulo) {
        SeleccionadorRectanguloContiene seleccionador = new SeleccionadorRectanguloContiene(rectangulo);
        getListaElementos().iterarDescendente(seleccionador);
        return seleccionador.getSeleccionados();
    }

    private class SeleccionadorPunto implements Visitante {

        private ElementoGrafico seleccionado;
        private Point2D punto;
        private double tolerancia;

        SeleccionadorPunto(Point2D punto, double tolerancia) {
            this.punto = punto;
            this.tolerancia = tolerancia; 
        }

        @Override
        public boolean visitar(ElementoGrafico nodoGrafico) {
            if (nodoGrafico.contiene(punto, tolerancia)) {
                seleccionado = nodoGrafico;
                return false;
            }
            return true;
        }

        public ElementoGrafico getSeleccionado() {
            return seleccionado;
        }
    }

    private class SeleccionadorRectanguloContiene implements Visitante {

        private ListaElementos seleccionados;
        private Rectangle2D rectangulo;

        SeleccionadorRectanguloContiene(Rectangle2D rectangulo) {
            this.rectangulo = rectangulo;
            seleccionados = new ListaElementos();
        }

        @Override
        public boolean visitar(ElementoGrafico elemento) {
            if (rectangulo.contains(elemento.getEncuadre())) {
                seleccionados.agregar(elemento);
            }
            return true;
        }

        public ListaElementos getSeleccionados() {
            return seleccionados;
        }
    }
}
